using System;
using System.Linq;
using System.Collections.Generic;
using Com.ContentRendering.Api.LoaderEngine;
using Com.ContentRendering.Api.ParserEngine;
using Com.ContentRendering.Api.RenderEngine;
using ContentRenderingApi.Node;
using ContentRenderingApi.Envelope;
using ContentRenderingApi.Data;
using ContentRenderingApi.Configuration;
using ContentRenderingApi.Extender;
using ContentRenderingApi.Skeleton;
using ContentRenderingApi.Path;
using System.Text;
using System.IO;
using Com.ContentRendering.Api.DataEngine;
using ContentRenderingApi.Exceptions;

namespace ContentRenderingApi
{
    /// <summary/>
    public abstract class AbstractTemplate
    {
        private string _name;
        private List<KeyValuePair<string, object>> _namedParameters;
        private List<object> _parameters;
        private TransformationConfiguration _transformationConfiguration;
        private bool _isCaching;
        private SpecialCharactersConfiguration _specialCharactersConfiguration;
        private PreprocessingSeparatorsConfiguration _preprocessingSeparatorsConfiguration;
        private Func<string, string> _documentOverride;
        private Action<int, string> _logger;

        /// <summary/>
        public AbstractTemplate(string name)
        {
            // set defaults
            this._namedParameters = new List<KeyValuePair<string, object>>();
            this._parameters = new List<object>();
            this._transformationConfiguration = new TransformationConfiguration();
            this._specialCharactersConfiguration = new SpecialCharactersConfiguration();
            this._preprocessingSeparatorsConfiguration = new PreprocessingSeparatorsConfiguration();
            this._name = name;
            this._isCaching = true;

            // set defaults
            this._specialCharactersConfiguration.Substitution = Constant.DEFAULT_SUBSTITUTION_SPECIAL_CHARACTER;
            this._specialCharactersConfiguration.Escape = Constant.DEFAULT_ESCAPE_SPECIAL_CHARACTER;
            this._specialCharactersConfiguration.If = Constant.DEFAULT_IF_SPECIAL_CHARACTER;
            this._specialCharactersConfiguration.IfNot = Constant.DEFAULT_IF_NOT_SPECIAL_CHARACTER;
            this._specialCharactersConfiguration.Start = Constant.DEFAULT_START_SPECIAL_CHARACTER;
            this._specialCharactersConfiguration.Child = Constant.DEFAULT_CHILD_SPECIAL_CHARACTER;
            this._specialCharactersConfiguration.End = Constant.DEFAULT_END_SPECIAL_CHARACTER;
            this._specialCharactersConfiguration.Transparent = Constant.DEFAULT_TRANSPARENT_SPECIAL_CHARACTER;
            this._specialCharactersConfiguration.Parent = Constant.DEFAULT_PARENT_SPECIAL_CHARACTER;

            // set defaults
            this._preprocessingSeparatorsConfiguration.ImportStart = Constant.DEFAULT_PREPROCESSING_IMPORT_START_SEPERATOR;
            this._preprocessingSeparatorsConfiguration.ImportEnd = Constant.DEFAULT_PREPROCESSING_IMPORT_END_SEPERATOR;
            this._preprocessingSeparatorsConfiguration.GetSetConstantStart = Constant.DEFAULT_PREPROCESSING_GET_SET_CONSTANT_START_SEPERATOR;
            this._preprocessingSeparatorsConfiguration.GetSetConstantEnd = Constant.DEFAULT_PREPROCESSING_GET_SET_CONSTANT_END_SEPERATOR;
            this._preprocessingSeparatorsConfiguration.GetSetConstantCenter = Constant.DEFAULT_PREPROCESSING_GET_SET_CONSTANT_CENTER_SEPERATOR;
            this._preprocessingSeparatorsConfiguration.GetSetFlagStart = Constant.DEFAULT_PREPROCESSING_GET_SET_FLAG_START_SEPERATOR;
            this._preprocessingSeparatorsConfiguration.GetSetFlagEnd = Constant.DEFAULT_PREPROCESSING_GET_SET_FLAG_END_SEPERATOR;
            this._preprocessingSeparatorsConfiguration.GetSetFlagCenter = Constant.DEFAULT_PREPROCESSING_GET_SET_FLAG_CENTER_SEPERATOR;
        }

        /// <summary/>
        protected abstract string LoadTemplate(string name);

        /// <summary/>
        public TransformationConfiguration TransformationConfiguration
        {
            get { return this._transformationConfiguration; }
        }

        /// <summary/>
        public bool IsCaching
        {
            set { this._isCaching = value; }
        }

        /// <summary/>
        public void AssignDocumentOverride(Func<string, string> documentOverride)
        {
            this._documentOverride = documentOverride;
        }

        /// <summary/>
        public void ApplyLogger(Action<int, string> logger)
        {
            this._logger = logger;
        }

        /// <summary/>
        public PreprocessingSeparatorsConfiguration CreatePreprocessingSeparatorsConfiguration()
        {
            return (PreprocessingSeparatorsConfiguration)this._preprocessingSeparatorsConfiguration.Clone();
        }

        /// <summary/>
        public void ApplyPreprocessingSeparatorsConfiguration(PreprocessingSeparatorsConfiguration preprocessingSeparatorsConfiguration)
        {
            List<string> values = new List<string>();

            // add each values
            values.Add(preprocessingSeparatorsConfiguration.ImportStart);
            values.Add(preprocessingSeparatorsConfiguration.ImportEnd);
            values.Add(preprocessingSeparatorsConfiguration.GetSetConstantStart);
            values.Add(preprocessingSeparatorsConfiguration.GetSetConstantCenter);
            values.Add(preprocessingSeparatorsConfiguration.GetSetConstantEnd);
            values.Add(preprocessingSeparatorsConfiguration.GetSetFlagStart);
            values.Add(preprocessingSeparatorsConfiguration.GetSetFlagCenter);
            values.Add(preprocessingSeparatorsConfiguration.GetSetFlagEnd);

            // if the unique counts are different than fail
            if (values.Count != values.Distinct().Count())
            {
                throw new PreprocessingSeparatorsNotUniqueException();
            }

            // check each value
            foreach (string value in values)
            {
                // is it set
                if (string.IsNullOrEmpty(value))
                {
                    throw new PreprocessingSeparatorsInvalidException();
                }
            }

            // record it
            this._preprocessingSeparatorsConfiguration = preprocessingSeparatorsConfiguration;
        }

        /// <summary/>
        public SpecialCharactersConfiguration CreateSpecialCharactersConfiguration()
        {
            return (SpecialCharactersConfiguration)this._specialCharactersConfiguration.Clone();
        }

        /// <summary/>
        public void ApplySpecialCharactersCustomization(SpecialCharactersConfiguration specialCharactersConfiguration)
        {
            List<char> values = new List<char>();

            // add each values
            values.Add(specialCharactersConfiguration.Substitution);
            values.Add(specialCharactersConfiguration.Escape);
            values.Add(specialCharactersConfiguration.If);
            values.Add(specialCharactersConfiguration.IfNot);
            values.Add(specialCharactersConfiguration.Start);
            values.Add(specialCharactersConfiguration.Child);
            values.Add(specialCharactersConfiguration.End);
            values.Add(specialCharactersConfiguration.Transparent);
            values.Add(specialCharactersConfiguration.Parent);

            // are the values unique
            if (values.Count != values.Distinct().Count())
            {
                throw new SpecialCharactersNotUniqueException();
            }

            // record it
            this._specialCharactersConfiguration = specialCharactersConfiguration;
        }
        
        /// <summary/>
        public void AddParameter(object value)
        {
            this._parameters.Add(value);
        }

        /// <summary/>
        public void AddNamedParameter(string name)
        {
            this._namedParameters.Add(new KeyValuePair<string, object>(name, string.Empty));
        }

        /// <summary/>
        public void AddNamedParameter(
            string name,
            object value)
        {
            this._namedParameters.Add(new KeyValuePair<string, object>(name, value));
        }
        
        /// <summary/>
        public string Render()
        {
            DocumentRender documentRender = new DocumentRender();
            StringBuilder stringBuilder = new StringBuilder();

            using (StringWriter writer = new StringWriter(stringBuilder))
            {
                // render the document
                documentRender.Render(
                    writer,
                    new InputNodeEnvelope(this.FetchNode()),
                    new InputDataEnvelope(this.FetchData()));
            }

            return stringBuilder.ToString();
        }

        /// <summary/>
        public void Render(
            Stream stream,
            Encoding encoding)
        {
            DocumentRender documentRender = new DocumentRender();

            // don't dispose of the stream ... that is the responsibility of the caller
            StreamWriter writer = new StreamWriter(stream, encoding);

            // render the document
            documentRender.Render(
                writer,
                new InputNodeEnvelope(this.FetchNode()),
                new InputDataEnvelope(this.FetchData()));

            // closing the writer closes the stream so just flush to make sure the contents are written
            writer.Flush();
        }

        /// <summary/>
        private AbstractNode FetchNode()
        {
            DocumentLoader documentLoader = new DocumentLoader(
                this.LoadTemplate,
                this._preprocessingSeparatorsConfiguration);

            // load the document from the name
            string loadedDocument = documentLoader.Load(this._name);

            // was a document override assigned
            if (this._documentOverride != null)
            {
                loadedDocument = this._documentOverride(loadedDocument);
            }

            DefaultNode node = new DefaultNode();

            DocumentParser documentParser = new DocumentParser(this._specialCharactersConfiguration);

            // convert the loaded document into a node structure
            documentParser.Parse(loadedDocument, new OutputNodeEnvelope(this._logger, node));

            return node;
        }

        /// <summary/>
        private AbstractData FetchData()
        {
            return new DefaultData(
                new ReflectionCache(),
                this._transformationConfiguration,
                this._isCaching,
                this._namedParameters.GetEnumerator(),
                this._parameters.GetEnumerator());
        }
    }
}
